package com.lap.framework.common.tool;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import lombok.experimental.UtilityClass;

@UtilityClass
public class MD5Util {

  /**
   * 计算字符串的 MD5 哈希值（32位小写）
   *
   * @param input 输入字符串
   * @return MD5 哈希值（32位小写）
   */
  public static String md5(String input) {
    if (input == null) {
      return null;
    }

    try {
      MessageDigest md = MessageDigest.getInstance("MD5");
      byte[] hash = md.digest(input.getBytes(StandardCharsets.UTF_8));
      return bytesToHex(hash);
    } catch (NoSuchAlgorithmException e) {
      throw new RuntimeException("MD5 algorithm not found", e);
    }
  }

  /**
   * 计算字节数组的 MD5 哈希值（32位小写）
   *
   * @param input 输入字节数组
   * @return MD5 哈希值（32位小写）
   */
  public static String md5(byte[] input) {
    if (input == null) {
      return null;
    }

    try {
      MessageDigest md = MessageDigest.getInstance("MD5");
      byte[] hash = md.digest(input);
      return bytesToHex(hash);
    } catch (NoSuchAlgorithmException e) {
      throw new RuntimeException("MD5 algorithm not found", e);
    }
  }

  /**
   * 计算文件的 MD5 哈希值（32位小写）
   *
   * @param file 输入文件
   * @return MD5 哈希值（32位小写）
   * @throws IOException 如果文件读取失败
   */
  public static String md5(File file) throws IOException {
    if (file == null || !file.exists()) {
      return null;
    }

    try (FileInputStream fis = new FileInputStream(file)) {
      MessageDigest md = MessageDigest.getInstance("MD5");
      byte[] buffer = new byte[8192];
      int length;

      while ((length = fis.read(buffer)) != -1) {
        md.update(buffer, 0, length);
      }

      byte[] hash = md.digest();
      return bytesToHex(hash);
    } catch (NoSuchAlgorithmException e) {
      throw new RuntimeException("MD5 algorithm not found", e);
    }
  }

  /**
   * 计算字符串的 MD5 哈希值（16位小写）
   *
   * <p>16位MD5实际上是32位MD5的中间16位
   *
   * @param input 输入字符串
   * @return MD5 哈希值（16位小写）
   */
  public static String md516(String input) {
    String md5_32 = md5(input);
    return md5_32 != null ? md5_32.substring(8, 24) : null;
  }

  /**
   * 计算字节数组的 MD5 哈希值（16位小写）
   *
   * <p>16位MD5实际上是32位MD5的中间16位
   *
   * @param input 输入字节数组
   * @return MD5 哈希值（16位小写）
   */
  public static String md516(byte[] input) {
    String md5_32 = md5(input);
    return md5_32 != null ? md5_32.substring(8, 24) : null;
  }

  /**
   * 计算字符串的 MD5 哈希值（32位大写）
   *
   * @param input 输入字符串
   * @return MD5 哈希值（32位大写）
   */
  public static String md5Upper(String input) {
    String md5 = md5(input);
    return md5 != null ? md5.toUpperCase() : null;
  }

  /**
   * 计算字节数组的 MD5 哈希值（32位大写）
   *
   * @param input 输入字节数组
   * @return MD5 哈希值（32位大写）
   */
  public static String md5Upper(byte[] input) {
    String md5 = md5(input);
    return md5 != null ? md5.toUpperCase() : null;
  }

  /**
   * 计算文件的 MD5 哈希值（32位大写）
   *
   * @param file 输入文件
   * @return MD5 哈希值（32位大写）
   * @throws IOException 如果文件读取失败
   */
  public static String md5Upper(File file) throws IOException {
    String md5 = md5(file);
    return md5 != null ? md5.toUpperCase() : null;
  }

  /**
   * 将字节数组转换为十六进制字符串
   *
   * @param bytes 字节数组
   * @return 十六进制字符串
   */
  private static String bytesToHex(byte[] bytes) {
    StringBuilder hexString = new StringBuilder();
    for (byte b : bytes) {
      String hex = Integer.toHexString(0xff & b);
      if (hex.length() == 1) {
        hexString.append('0');
      }
      hexString.append(hex);
    }
    return hexString.toString();
  }

  /**
   * 验证字符串的 MD5 哈希值是否匹配
   *
   * @param input 输入字符串
   * @param md5Hash 预期的 MD5 哈希值
   * @return 是否匹配
   */
  public static boolean verify(String input, String md5Hash) {
    if (input == null || md5Hash == null) {
      return false;
    }
    return md5(input).equalsIgnoreCase(md5Hash);
  }

  /**
   * 验证字节数组的 MD5 哈希值是否匹配
   *
   * @param input 输入字节数组
   * @param md5Hash 预期的 MD5 哈希值
   * @return 是否匹配
   */
  public static boolean verify(byte[] input, String md5Hash) {
    if (input == null || md5Hash == null) {
      return false;
    }
    return md5(input).equalsIgnoreCase(md5Hash);
  }

  /**
   * 验证文件的 MD5 哈希值是否匹配
   *
   * @param file 输入文件
   * @param md5Hash 预期的 MD5 哈希值
   * @return 是否匹配
   * @throws IOException 如果文件读取失败
   */
  public static boolean verify(File file, String md5Hash) throws IOException {
    if (file == null || md5Hash == null || !file.exists()) {
      return false;
    }
    return md5(file).equalsIgnoreCase(md5Hash);
  }
}
